using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Xml.XPath;
using Microsoft.SharePoint;
using PS.Practices.Provisioner;
using PS.Practices.Util;

namespace PS.Practices.SiteBuilder.Features.Schema
{
    static class ListParser
    {
        internal static void FixupListACL(SPList list, SPWeb web, XPathNavigator xFixupAcl)
        {
            Logger.Info("Configuring list ACL: " + list.Title);

            if (xFixupAcl != null)
            {
                string mode = ParserUtil.GetAttribute( xFixupAcl, "RoleAssignments");
                if (!string.IsNullOrEmpty(mode))
                {
                    if (mode.StartsWith("Break") && !list.HasUniqueRoleAssignments)
                    {
                        Logger.Info("Breaking role inheritance: " + mode);
                        list.BreakRoleInheritance(mode == "BreakAndCopy");
                    }
                }

                foreach (XPathNavigator node in xFixupAcl.SelectChildren(XPathNodeType.Element))
                {
                    switch (node.LocalName)
                    {
                        case "PrincipalRole":
                            string role =  ParserUtil.GetAttribute(node, "Role");
                            string name = ParserUtil.GetAttribute(node, "Name");
                            string action = ParserUtil.GetAttribute(node, "Action");
                            string type = ParserUtil.GetAttribute(node, "Type");

                            bool doAdd = action == "Add";
                            bool doRemove = action == "Remove";

                            if (!doAdd && !doRemove)
                            {
                                Logger.Info("Unsupported action attribute in " + node.OuterXml);
                                continue;
                            }

                            name = ParserUtil.ReplaceLiveFields(name, list, web);
                            
                            SPPrincipal principal = null;
                            
                            switch(type)
                            {
                                case "SiteGroup":
                                    principal = AclUtil.ResolveSiteGroup(name, web);
                                    break;
                                    
                                case "User":
                                    principal = AclUtil.ResolveUser(name, web);
                                    break;
                                    
                                default:
                                    Logger.Info("Unsupported principal type : " + type + " in " + node.OuterXml);
                                    break;
                            }

                            if (principal != null)
                            {
                                if (doAdd)
                                    AclUtil.AddPrincipal(principal, role, list, web);
                                else
                                {
                                    AclUtil.RemovePrincipal(principal, list, web);
                                }
                            }

                            break;

                        default:
                            Logger.Info("Unsupported node: " + node.OuterXml);
                            break;
                    }
                }

                web.Update();
            }
        }

        internal static void FixupLists(SPWeb web, XPathNavigator doc)
        {
            XPathNavigator xFixupListsNode = doc.SelectSingleNode("FixupLists");
            if (xFixupListsNode != null)
            {
                string attr = ParserUtil.GetAttribute(xFixupListsNode, "SyncCTypeLocalFieldSettings");
                if (!string.IsNullOrEmpty(attr))
                {
                    bool val;
                    bool.TryParse(attr, out val);
                    if (val)
                    {
                        try
                        {
                            ListUtil.SyncReqHidFieldStatusInLists(web);
                        }
                        catch (Exception e)
                        {
                            Logger.Error(e);
                        }
                    }
                }
            }
            else
                return;

            XPathNodeIterator xFixupLists = xFixupListsNode.Select("List");
            foreach (XPathNavigator xFixupList in xFixupLists)
            {
                string listName = ParserUtil.GetAttribute(xFixupList, "Title");
                
                if (string.IsNullOrEmpty(listName))
                {
                    continue;
                }
                SPList list=null;
                try
                {
                    list = web.Lists[listName];
                } catch(IndexOutOfRangeException)
                {
                }
                catch (ArgumentException)
                { }
                
                if (list == null)
                {
                    continue;
                }

                ProcessList(list, xFixupList);
            }
        }

        internal static void ProcessList(SPList list, XPathNavigator script)
        {
            SPWeb web = list.ParentWeb;
            Logger.Info("Processing list " + list.Title);

            bool delete =  ParserUtil.GetAttributeBool( script, "Delete", false);
            if (delete)
            {
                list.AllowDeletion = true;
                list.Update();
                list.Delete();
                //web.Update();
                Logger.Info("List deleted");
                return;
            }

            string attr = ParserUtil.GetAttribute(script, "EnableContentTypes");
            if (!string.IsNullOrEmpty(attr))
            {
                bool val;
                bool.TryParse(attr, out val);
                list.ContentTypesEnabled = val;
                Logger.Info("Setting list attribute EnableContentTypes = " + val);
                list.Update();
            }

            foreach (XPathNavigator node in script.SelectChildren(XPathNodeType.Element) )
            {
                bool skipUpdate = false;
                switch(node.LocalName)
                {
                    case "SetSendToLocation":
                            
                        string strName = ParserUtil.GetAttribute(node, "Name");
                        string strUrl = ParserUtil.GetAttribute(node, "Url");
                            
                        if (string.IsNullOrEmpty(strName))
                        {
                            Logger.Info("Required attribute Name is missing: " + node.OuterXml);
                            continue;
                        }
                        if (string.IsNullOrEmpty(strUrl))
                        {
                            Logger.Info("Required attribute Url is missing: " + node.OuterXml);
                            continue;
                        }

                        strUrl = ParserUtil.ReplaceLiveFields(strUrl, web);
                        if (strUrl.StartsWith("./"))
                        {
                            strUrl = web.Url + strUrl.Substring(2);
                        }
                        else
                        {
                            if (strUrl.StartsWith("~/"))
                            {
                                SPWeb rootWeb = web.Site.RootWeb;
                                strUrl = rootWeb.Url.TrimEnd('/') + strUrl.Substring(1);
                            }
                        }
                            
                        list.SendToLocationName = strName;
                        list.SendToLocationUrl = strUrl;

                        break;
                            
                    case "UpdateTitle":
                        Dictionary<string, string> dic = null;
                        string set;
                        dic = ReadReplaceInfo(dic, node, out set);

                        ParserUtil.UpdateListTitle(list, set, dic);
                        break;
                    case "AddCTypes":
                        List<string> addctypes = ReadContentTypeRefs(node.SelectChildren(XPathNodeType.Element));
                        foreach (string id in addctypes)
                        {
                            ListUtil.AddCType(list, id);
                        }
                        break;
                    case "RemoveCTypes":
                        List<string> remctypes = ReadContentTypeRefs(node.SelectChildren(XPathNodeType.Element));
                        foreach (string id in remctypes)
                        {
                            ListUtil.RemoveCType(list, id);
                        }
                        break;
                    case "ReorderCTypes":
                        ListUtil.ReorderCTypes(list);
                        skipUpdate = true;
                        break;

                    case "FixupACL":
                        FixupListACL(list, web, node);
                        break;

                    case "FixupView":
                        FixupView(list, node);
                        break;
                    case "UpdateFields":
                        UpdateListFields(list, web, node);
                        break;
                    case "AddFields":
                        AddListFields(list, node);
                        break;
                    default:
                        Logger.Info("Unsupported node : " + node.OuterXml);
                        break;
                }

                if (!skipUpdate)
                    list.Update();
            }
        }

        internal static void AddListFields(SPList list, XPathNavigator node)
        {
            try
            {
                foreach (XPathNavigator xAdd in node.Select("Add"))
                {
                    XPathNavigator xField = xAdd.SelectSingleNode("Field");
                    if (xField == null)
                        continue;

                    string fieldXml = xField.OuterXml;
                    Logger.Info("Adding field: " + fieldXml);
                    bool addToDefView = ParserUtil.GetAttributeBool(xAdd, "AddToDefaultView", true);

                    list.Fields.AddFieldAsXml(fieldXml, addToDefView, SPAddFieldOptions.AddToAllContentTypes);
                }
            }
            catch (Exception e)
            {
                Logger.Info("Exception occured while adding fields");
                Logger.Error(e);
            }

        }

        internal static void FixupView(SPList list, XPathNavigator node)
        {
            string viewName = ParserUtil.GetAttribute(node, "Name");
            if (string.IsNullOrEmpty(viewName))
                return;
            SPView view = list.Views[viewName];
            if (view == null)
                return;
            
            foreach (XPathNavigator childNode in node.SelectChildren(XPathNodeType.Element) )
            {
                switch (childNode.LocalName)
                {
                    case "InjectCTypeFields":
                        string sPos = ParserUtil.GetAttribute(childNode, "Position");
                        int pos = 0;
                        if (!string.IsNullOrEmpty(sPos))
                        {
                            int.TryParse(sPos, out pos);
                            if (pos > view.ViewFields.Count)
                                pos = view.ViewFields.Count;
                        }
                        StringCollection less = new StringCollection();

                        foreach (XPathNavigator n in childNode.Select("LessField"))
                        {
                            string name =  ParserUtil.GetAttribute(n, "Name");
                            less.Add(name);
                        }

                        ListUtil.InjectFieldsFromCTypes(view, list.ContentTypes, pos, less);
                        break;
                    default:
                        Logger.Info("Unsupported node : " + node.OuterXml);
                        break;
                }
            }
        }

        internal static Dictionary<string, string> ReadReplaceInfo(Dictionary<string, string> dic, XPathNavigator node, out string set)
        {
            set = ParserUtil.GetAttribute(node, "Set");
            foreach (XPathNavigator element in node.SelectChildren(XPathNodeType.Element))
            {
                if (string.Compare(element.LocalName, "Replace", true)>=0)
                {
                    if (dic==null) dic = new Dictionary<string, string>(16);
                    dic.Add( ParserUtil.GetAttribute(element, "Match"), element.Value);
                }
            }
            return dic;
        }

        internal static List<string> ReadContentTypeRefs(XPathNodeIterator xNodeList)
        {
            List<string> ret = new List<string>(xNodeList.Count);
            foreach (XPathNavigator element in xNodeList)
            {

                if (element.LocalName != "ContentTypeRef")
                    continue;

                string attribute = ParserUtil.GetAttribute(element, "ID");
                if (!string.IsNullOrEmpty(attribute))
                    ret.Add(attribute);
            }
            return ret;
        }

        internal static void UpdateListFields(SPList list, SPWeb web, XPathNavigator node)
        {
            try
            {
                foreach (XPathNavigator xField in node.Select("Field"))
                {
                    string name = ParserUtil.GetAttribute(xField, "DisplayName");
                    string staticname = ParserUtil.GetAttribute(xField, "StaticName");
                    if (string.IsNullOrEmpty(name) && string.IsNullOrEmpty(staticname))
                        continue;

                    SPField field = null;
                    try
                    {
                        if (!string.IsNullOrEmpty(staticname))
                            field = list.Fields.GetFieldByInternalName(staticname);
                        else
                            field = list.Fields[name];
                    }
                    catch (ArgumentException)
                    {
                    }

                    if (field == null)
                    {
                        Logger.Info("Could not find field " + name + ":" + staticname + " in list " + list.Title);
                        continue;
                    }

                    Logger.Info("Updating field " + field.StaticName + " in list " + list.Title + ". ID=" + field.Id + ", SourceID=" + field.SourceId );

                    if (field.Sealed)
                    {
                        field.Sealed = false;
                        Logger.Info("Field was sealed");
                    }

                    XPathNavigator xDescription = xField.SelectSingleNode("Description");
                    if (xDescription != null)
                    {
                        field.Description = ParserUtil.ReplaceLiveFields(xDescription.Value, list, web);

                        Logger.Info("Description set to: " + field.Description);
                    }

                    if (xField.SelectSingleNode("DumpFields")!=null)
                    {
                        foreach (SPField spField in list.Fields)
                        {
                            Logger.Info(list.Title + ": {" + spField.Id + "} -- " + spField.StaticName + " : " + spField.Title);
                        }
                    }

                    string addToCTypes = ParserUtil.GetAttribute(xField, "LinkToLocalCTypes");
                    if (!string.IsNullOrEmpty(addToCTypes))
                    {
                        bool res;
                        bool.TryParse(addToCTypes, out res);
                        if (res)
                        {
                            Logger.Info("Adding field " + field.StaticName + " to all local list content types ");
                            ListUtil.LinkFieldToListCTypes(field, list);
                        }
                    }

                    SPFieldChoice choice = field as SPFieldChoice;
                    SPFieldMultiChoice mchoice = field as SPFieldMultiChoice;
                    if (choice != null || mchoice != null)
                    {
                        XPathNavigator xChoices = xField.SelectSingleNode("Choices");
                        if (xChoices != null)
                        {
                            StringBuilder sb = new StringBuilder();
                            StringCollection coll = choice != null ? choice.Choices : mchoice.Choices;
                            if (! string.IsNullOrEmpty(ParserUtil.GetAttribute(xChoices, "Flush")))
                            {
                                coll.Clear();
                                sb.Append("#Flush# ");
                            }
                            foreach (XPathNavigator n in xChoices.SelectChildren(XPathNodeType.Element))
                            {
                                string s = ParserUtil.ReplaceLiveFields(n.Value, list, web);
                                sb.Append(s + "; ");
                                coll.Add(s);
                            }

                            Logger.Info("Choices added: " + sb);
                        }
                    }

                    XPathNavigator xDefault = xField.SelectSingleNode("Default");
                    if (xDefault != null)
                    {
                        string prevDef = field.DefaultValue;
                        string curDef = ParserUtil.ReplaceLiveFields(xDefault.Value, list, web);
                        field.DefaultValue = curDef;

                        Logger.Info("Default value of '" + prevDef + "' replaced with '" + field.DefaultValue + "'");
                    }

                    field.Update(true);

                    Logger.Info("Final default value: '" + field.DefaultValue + "'");
                }
            } catch(Exception e)
            {
                Logger.Info("Exception occured while updating fields");
                Logger.Error(e);
            }
        }
    }
}
